//
//  NetServer.m
//  CommandCenter08
//
//  Created by Matt Roman on 7/16/08.
//  Copyright 2008 University of Oklahoma. All rights reserved.
//

#import "NetServer.h"
#import <arpa/inet.h>
#import <sys/socket.h>
#import <sys/time.h>
#import <sys/ioctl.h>
#import <fcntl.h>
#import <netdb.h>
#import <unistd.h>

static void callbackRoutine(CFSocketRef inSockRef, CFSocketCallBackType inType, CFDataRef inAddress, const void* inData, void* inContext);

@interface NetServer (Private)
- (void)socketWriteData;
- (void)socketReadData;
@end

@implementation NetServer

- (id)init
{
	//int i;
	
	self = [super init];
	socketRef = NULL;
	nSocketRunLoopSource = NULL;
	nDelegate = nil;
	nDataOut = nil;
	nDataIn = [[NSMutableData alloc] init];
	bzero(&blankAddress, sizeof(struct sockaddr_in));
	bzero(&aConnection, sizeof(struct sockaddr_in));
	//for (i=0; i<MAXNUMCONNECTIONS; i++) {
	//	connectionsList[i] = blankAddress;
	//}
	numConnections = 0;
	return self;
}

- (void)dealloc
{
	nDelegate = nil;
	
	// Unschedule socket from runloop
	[self unscheduleFromRunLoop];
	
	[self close];
	
	[nDataIn release];
	nDataIn = nil;
	
	[super dealloc];
}

+ (NetServer*)UDPServerSocket
{
	NetServer*	netsocket;
	BOOL		success = NO;
	
	netsocket = [[[NetServer alloc] init] autorelease];
	
	if([netsocket openSocket])
		if([netsocket scheduleOnCurrentRunLoop]) success = YES;
	return (success ? netsocket : nil);
}

- (void)setDelegate:(id)inDelegate
{
	nDelegate = inDelegate;
}

- (BOOL)openSocket
{
	CFSocketContext		socketContext;
	CFOptionFlags		socketCallbacks;
	CFOptionFlags		socketOptions;
	int					socketFlags;
	
	if(socketRef == NULL){
		int nativeSocket = socket(AF_INET, SOCK_DGRAM, 0);
		if(nativeSocket < 0) return NO;
		
		bzero(&socketContext, sizeof(socketContext));
		socketContext.info = self;
		socketCallbacks = kCFSocketReadCallBack + kCFSocketWriteCallBack;
		socketFlags = fcntl(nativeSocket, F_GETFL, 0);
		if(socketFlags >= 0){
			if(fcntl(nativeSocket, F_SETFL, socketFlags | O_NONBLOCK) >= 0)
			{
				socketRef = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, socketCallbacks, &callbackRoutine, &socketContext);
			}
		}
		
		if(!socketRef) close(nativeSocket);
		else {
			socketOptions = kCFSocketAutomaticallyReenableReadCallBack;
			CFSocketSetSocketFlags(socketRef, socketOptions);
			return YES;
		}
	}
	return NO;
}
- (void)close
{	
	//int i;
	// Unschedule from runloop
	[self unscheduleFromRunLoop];
	
	if(socketRef != NULL){
		close(CFSocketGetNative(socketRef));
		CFSocketInvalidate(socketRef);
		CFRelease(socketRef);
		socketRef = NULL;
	}
	[nDataOut release];
	nDataOut = nil;
	aConnection = blankAddress;
	//for(i=0;i<MAXNUMCONNECTIONS;i++)
	//{
	//	connectionsList[i] = blankAddress;
	//}
	numConnections = 0;
}

- (int)numConnections
{
	return numConnections;
}

- (BOOL)scheduleOnCurrentRunLoop
{
	return [self scheduleOnRunLoop:[NSRunLoop currentRunLoop]];
}

- (BOOL)scheduleOnRunLoop:(NSRunLoop*)inRunLoop
{
	CFRunLoopRef runloop;
	if((socketRef == NULL) || nSocketRunLoopSource) return NO;
	
	// Remove ourselves from any other runloop we might be apart of
	[self unscheduleFromRunLoop];
	
	runloop = [inRunLoop getCFRunLoop];
	if(!runloop) return NO;
	
	nSocketRunLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socketRef, 0);
	if(!nSocketRunLoopSource) return NO;
	
	CFRunLoopAddSource(runloop, nSocketRunLoopSource, kCFRunLoopDefaultMode);
	return YES;
}

- (void)unscheduleFromRunLoop
{
	// If our CFSocketRef has not been created than it probably hasn't been scheduled yet
	if( (socketRef == NULL) || nSocketRunLoopSource == NULL )
		return;
	
	// If the runloop source is not valid, return
	if( !CFRunLoopSourceIsValid( nSocketRunLoopSource ) )
		return;
	
	// Invalidate and release the runloop source
	CFRunLoopSourceInvalidate( nSocketRunLoopSource );
	CFRelease( nSocketRunLoopSource );
	
	// Reset our reference to the runloop source
	nSocketRunLoopSource = NULL;
}

- (BOOL)bindServerToPort:(UInt16)inPort
{	
	if(socketRef == NULL) return NO;
	
	bzero(&socketAddress, sizeof(socketAddress));
	socketAddress.sin_family = AF_INET;
	socketAddress.sin_port = htons(inPort);
	socketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
	
	if(bind(CFSocketGetNative(socketRef), (struct sockaddr*)&socketAddress, sizeof(socketAddress)) < 0) return NO;
	
	socketAddressLen = sizeof(socketAddress);
	
	[nDataIn setLength:0];
	return YES;
}

///////////////////////////////////////////////////////////////////////////////////////////
// sending data 
- (void)sendData:(NSData*)inData
{	
	if(socketRef == NULL) return;
	// Create outgoing buffer if needed
	if(!nDataOut) nDataOut = [[NSMutableData alloc] initWithCapacity:[inData length]];
	// Append specified bytes to the outgoing buffer
	[nDataOut appendBytes:[inData bytes] length:[inData length]];
	// Attempt to write data to the socket
	[self socketWriteData];
}
- (void)sendString:(NSString*)inString encoding:(NSStringEncoding)inEncoding
{
	[self sendData:[inString dataUsingEncoding:inEncoding]];
}
- (void)socketWriteData
{	
	//int i;
	int amountSent;
	
	if(numConnections<=0) {
		[nDataOut setLength:0];
		return;
	}
	//for(i=0;i<numConnections;i++){
	else{
		amountSent = sendto(CFSocketGetNative(socketRef), [nDataOut bytes], [nDataOut length], 0, (struct sockaddr*)&aConnection, socketAddressLen);
	}
	
	if(amountSent == [nDataOut length]){
		CFSocketDisableCallBacks(socketRef, kCFSocketWriteCallBack);
	}
	else if(amountSent >= 0){
		CFSocketEnableCallBacks(socketRef, kCFSocketWriteCallBack);
	}
	else{
		CFSocketDisableCallBacks(socketRef, kCFSocketWriteCallBack);
		NSLog(@"sending data error");
	}
	[nDataOut replaceBytesInRange:NSMakeRange(0, amountSent) withBytes:NULL length:0];
	if([nDataOut length] == 0)
		if([nDelegate respondsToSelector:@selector( netDataSent: )])
			[nDelegate netDataSent:self];
}
//////////////////////////////////////////////////////////////////////////////////////////
// reading data
- (NSData*)readData
{
	NSData* dataRead;
	
	if([nDataIn length] == 0) return nil;
	
	dataRead = [NSData dataWithData:nDataIn];
	if(!dataRead) return nil;
	
	// reset incomming data buffer since we have read in all the data
	[nDataIn setLength:0];
	
	return dataRead;
}
- (NSData*)readData:(unsigned)inAmount
{
	NSData *readData;
	unsigned amountToRead;
	if([nDataIn length] == 0) return nil;
	
	amountToRead = MIN(inAmount,[nDataIn length]);
	
	readData = [NSData dataWithBytes:[nDataIn bytes] length:amountToRead];
	if(!readData) return nil;
	[nDataIn replaceBytesInRange:NSMakeRange(0, amountToRead) withBytes:NULL length:0];
	
	return readData;
}
- (NSString*)readString:(NSStringEncoding)inEncoding
{
	NSString * stringRead;
	
	if([nDataIn length] == 0) return nil;
	
	stringRead = [[[NSString alloc] initWithData:nDataIn encoding:inEncoding] autorelease];
	if(!stringRead) return nil;
	
	// reset incomming data buffer since we have read in all the data
	[nDataIn setLength:0];
	
	return stringRead;
}
- (void)socketReadData
{
	//int i,j;
	unsigned oldBufferLength;
	int		amountAvaliable,amountRead;
	void*	readBuffer;
	struct sockaddr_in	theirAddress;
	
	oldBufferLength = [nDataIn length];
	
	// determine how many bytes are avaliable on the socket
	ioctl(CFSocketGetNative(socketRef), FIONREAD, &amountAvaliable);
	
	readBuffer = malloc((amountAvaliable == 0)?1:amountAvaliable);
	
	if(!readBuffer) return;
	// attempt to read in data
	amountRead = recvfrom(CFSocketGetNative(socketRef), readBuffer, (amountAvaliable == 0)?1:amountAvaliable, 0, (struct sockaddr*)&theirAddress, &socketAddressLen);
	
	NSString* location = [NSString stringWithUTF8String:inet_ntoa(theirAddress.sin_addr)];
	// if their address is not in the connections list add it
	//for(i=0;i<numConnections;i++)
	//{
	if(![[NSString stringWithUTF8String:inet_ntoa(aConnection.sin_addr)] isEqual:location] && aConnection.sin_addr.s_addr != blankAddress.sin_addr.s_addr){
		free(readBuffer);
		return;
	}
	//connectionsList[i] = theirAddress;
	if(amountRead > 0) [nDataIn appendBytes:readBuffer length:amountRead];
	
	if(aConnection.sin_addr.s_addr == blankAddress.sin_addr.s_addr){	// if there was no previous connection create a new one
		aConnection = theirAddress;
		numConnections = 1;
		if([nDelegate respondsToSelector:@selector(netServerConnection:)])
			[nDelegate netServerConnection:self];
	}
	else if(amountRead <= 0){ // if no data is received the client disconnected
		//for(j=i;j<MAXNUMCONNECTIONS-1;j++){
		//	connectionsList[j] = connectionsList[j+1];
		//}
		//connectionsList[j] = blankAddress;
		//if(numConnections > 0) numConnections--;
		aConnection = blankAddress;
		numConnections = 0;
		if([nDelegate respondsToSelector:@selector(netServerDisconnected:)])
			[nDelegate netServerDisconnected:self];
		
	}
	free(readBuffer);
	
	if([nDataIn length] <= oldBufferLength) return;
	if([nDelegate respondsToSelector:@selector(netDataAvailable:amount:)])
		[nDelegate netDataAvailable:self amount:[nDataIn length]];
}

- (void) flushBuffers
{
	if(nDataOut) [nDataOut setLength:0];
	[nDataIn setLength:0];
}
@end

void callbackRoutine(CFSocketRef inSockRef, CFSocketCallBackType inType, CFDataRef inAddress, const void* inData, void* inContext)
{
	NetServer* netsocket;
	
	netsocket = (NetServer*)inContext;
	if(!netsocket) return;
	switch (inType) {
		case kCFSocketReadCallBack:
			[netsocket socketReadData];
			break;
		case kCFSocketWriteCallBack:
			[netsocket socketWriteData];
			break;
		default:
			break;
	}
}